iT邦幫忙

2023 iThome 鐵人賽

DAY 7
0

前言

當你開始學習程式語言時,陣列(Array)通常是其中一個最早接觸到的資料結構(在leetcode裡面的總題目超過2000題,但Array占了1400多題,就知道這有多重要~)。

陣列是用來儲存一系列相同類型的資料的集合,並將它們存在連續的記憶體空間中。

關於陣列的重要資訊:

  • 相同類型的元素集合:陣列是一個包含相同類型元素的集合,這些元素可以是整數、浮點數、字串等等,但同一個陣列只能儲存相同類型的資料。
  • 連續的記憶體空間:陣列中的元素在記憶體中是連續存放的,這表示如果你有一個包含10個整數的陣列,這10個整數將按照順序在記憶體中佔用一塊連續的空間。
  • 預先知道大小:在建立陣列時,你需要事先知道陣列的大小,這表示你必須決定要儲存多少元素在陣列中。如果需要更多空間,你需要重新建立一個更大或更小的陣列,然後把舊陣列的資料複製過去。
  • 使用索引存取元素:每個陣列元素都有一個唯一的索引(index),從0開始遞增。我們通常會使用這個索引來存取和修改陣列中的元素。
  • 多維陣列:除了一維陣列,你還可以建立多維陣列,就像是表格或矩陣。例如,你可以建立一個二維陣列,它就像是一個格子的集合,有行和列。
  • 容量限制:陣列的大小限制在約40億個元素,這是一個非常大的數字。但在任何指定的維度中,最大索引值約為0x7FEFFFFF。如果需要更大的陣列,你可以在64位環境中進行特殊設定。
  • 泛型介面:單一維陣列實作了泛型介面,這意味著你可以使用泛型方式操作陣列中的元素,這在某些情況下很方便。

總之,陣列是一個用於儲存相同類型元素的基本資料結構,並且非常有用。由於它們的大小是固定的,因此在某些情況下可能會有記憶體浪費的問題。
進一步學習程式設計時,你可能會遇到更具彈性的資料結構,如列表(List)和字典(Dictionary),它們可以更靈活地處理資料。

綜合以上,我們可以知道,陣列的優點在於快速的隨機存取,缺點則是新增或移除 element 的開銷較大。

那如果換個方式來解釋Array的話:

想像一個有很多小格子的玩具盒子,每個小格子只能放一個玩具。這個玩具盒子只能裝相同類型的玩具,比如只能裝卡通人物,不能混著放車子。

你可以選擇要多大的盒子,比如一個10個格子的盒子或20個格子的盒子,但一旦選擇了,就不能再改變盒子的大小。所以,陣列就像是一個特殊的盒子,可以用來整齊地存放相同類型的玩具,每個玩具都有自己的位置,方便找到。

上面有提到陣列型別要一致,當你需要處理不同型別的數據(像是姓名,姓別,生日等等的,可能會有字串或是整數日期等等),通常無法將它們存儲在單個陣列中,因為陣列的元素必須具有相同的型別。

在這種情況下,你可以使用更複雜的數據結構,如集合(Collection)。

  1. 集合(Collection):集合是更靈活的數據結構,它允許你存儲和操作不同型別的數據。
    List 是一種動態陣列,可以容納多個元素,並提供了方便的方法來添加、刪除、查詢和操作這些元素。
    與傳統的陣列不同,List 具有可動態調整大小的能力,因此你可以根據需要添加或移除元素,而不必擔心容量的限制。
    在 C# 中,List 是一個泛型類型,這意味著你可以指定 List 存放的元素的類型,例如 List<int> 表示只能存放整數的 List。

當使用 List<T> 時, 是泛型型別的參數,它告訴 List 儲存的元素類型應該是什麼。這讓我們在使用 List 在處理相同類型的元素時非常方便,因為它能夠提供類型安全性,確保你只能將特定類型的元素添加到列表中。例如:

    List<int> numbers = new List<int>();
    numbers.Add(11);
    numbers.Add(42);
    numbers.Add(399);
    int secondNumber = numbers[1]; // 存取第二個數字 42

上面的程式碼可以看到使用 List<int> ,表示這個列表只能包含整數元素,並且編譯器會強制執行這一點。
使用集合時,你可以更輕鬆地添加、刪除和檢索數據,而不需要手動進行類型轉換。
至於甚麼是 < object>呢?

當我們使用 List<object>時,< object>表示這個列表可以包含任何 C# 物件的類型,因為 object 是所有 C# 類型的根類型。
讓 List< object>變成了一個通用的容器,可以容納各種不同類型的元素,就像是一個萬用盒子。例如:

```csharp
List<object> data = new List<object>();
data.Add(42);
data.Add("Hello");
data.Add(3.14);
data.Add(true);
```

我們可以把整數、字串、浮點數和布林值都添加到 List 中,因為它不限制元素的類型。
但是在使用 List 需要謹慎處理,因為你需要在使用這些元素時確保它們的實際類型,就像在玩具盒中找到特定的玩具一樣。
這種通用性使得 List<object> 在某些情況下很有用,但同時也可能導致類型錯誤,因此需要謹慎使用。

  1. 自定義類別:如果你有不同型別的數據,但這些數據都具有相關性,可以考慮創建一個自定義的類別,該類別包含這些數據的屬性。然後,你可以創建該類別的實例並存儲在陣列或集合中。

    class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
    
    Person[] people = new Person[]
    {
        new Person { Name = "Alice", Age = 30 },
        new Person { Name = "Bob", Age = 25 },
        new Person { Name = "Charlie", Age = 35 }
    };
    

HashSet - 集合

HashSet<T> 是一個集合,它可以容納相同類型的元素,但跟上面的陣列比起來不允許重複。

它會自動排序元素,這樣在去查找元素會變得非常快速,但新增和移除元素可能會比 List<T> 花更多時間。

下面程式碼關於List跟HashSet的差異比較

List<int> items1 = new List<int>();
items1.Add(1);
items1.Add(99);
items1.Add(1);
Console.WriteLine(items1.Count);//因為數字可以重複,所以顯示結果是3個

HashSet<int> uniqueNumbers = new HashSet<int>();
uniqueNumbers.Add(1);
uniqueNumbers.Add(99);
uniqueNumbers.Add(1);
Console.WriteLine(uniqueNumbers.Count);

可以看到下圖,因為HashSet的值不可以重複,所以使用Count的方法算出來只有2個,但如果用List的話是可以重複值的,所以是3個。
https://ithelp.ithome.com.tw/upload/images/20230919/20151470INiCCsVKFs.png

Dictionary<TKey, TValue> - 字典

Dictionary<TKey, TValue> 是一種以鍵值對(Key-Value Pair)方式存儲元素的集合。

每個元素都有一個唯一的鍵,並對應到一個值。鍵必須是唯一的,所以它們不允許重複。這使得你可以快速根據鍵查找對應的值。

Dictionary<int, string> cities = new Dictionary<int, string>();
cities.Add(11, "城市 A");
cities.Add(2222, "城市 B");
cities.Add(39, "城市 C");
string city = cities[39]; // 根據鍵 39 查找對應的城市名稱

排序和查找

在處理集合時,需要注意排序和查找的效能。

使用 .Sort() 方法可以對集合進行排序,而使用 .OrderBy() 可以返回已排序的新集合,但原本的集合並不會被更改。

List<int> numbers = new List<int> { 5, 1, 3, 9, 0 };
numbers.Sort(); // 對集合進行排序,會改變原集合的順序
var newItems = numbers.OrderBy(n => n); // 返回已排序的新集合,原集合不變

此外,在處理集合時,請小心更改集合內容。如果在迭代或排序過程中更改集合,可能會導致不可預期的結果。

字串轉成 Dictionary

有時,我們需要統計一個字串中不同元素的出現次數。你可以使用 Split 將字串分割成元素,然後使用 Dictionary 來統計它們的次數。

string s = "A,B,C,A,D,C,E";
string[] items = s.Split(',');

var maps = new Dictionary<string, int>();

foreach (var item in items)
{
    if (maps.ContainsKey(item))
    {
        maps[item] += 1;
    }
    else
    {
        maps.Add(item, 1);
    }
}

foreach (var item in maps)
{
    Console.WriteLine($"元素 = {item.Key}, 出現次數 = {item.Value}");
}

這樣的程式碼可以將字串中不同元素的出現次數統計出來,並儲存在 Dictionary 中。

當你需要將字串分割成陣列時,通常會使用 Split 方法,這是一種常見的作法,String.Split 方法會根據一或多個分隔符號來分割輸入字串,以建立子字串陣列。。以下是一些不同情境下的範例參考:

  1. 逗號分割:當你有一個包含多個項目的字串,項目之間使用逗號分隔時,可以使用 Split 方法將其分割成陣列。

    string products = "PC,Printer,Monitor,Mouse,Keyboard";
    string[] prodItems = products.Split(',');
    
    foreach (string item in prodItems)
    {
        Console.WriteLine(item);
    }
    

    這會將字串分割成包含各個產品的陣列。
    https://ithelp.ithome.com.tw/upload/images/20230919/201514706WRSJpBZFr.png

  2. 空白分割:如果字串中的項目是使用空格分隔的,你可以使用空格作為分隔符。

    string products = "PC Printer Monitor Mouse Keyboard";
    string[] prodItems = products.Split(' ');
    
    foreach (string item in prodItems)
    {
        Console.WriteLine(item);
    }
    

    這會將字串分割成包含各個產品的陣列,每個產品之間由空格分隔。
    https://ithelp.ithome.com.tw/upload/images/20230919/201514701vOPKLHkmN.png

  3. 自訂分隔符:如果你的字串使用自訂的分隔符,例如分號 ; ,你可以指定該分隔符作為參數傳遞給 Split 方法。

    string data = "apple;banana;cherry;date";
    string[] fruits = data.Split(';');
    
    foreach (string fruit in fruits)
    {
        Console.WriteLine(fruit);
    }
    

    這個範例將字串分割成包含各種水果的陣列,並使用分號 ; 作為分隔符。
    https://ithelp.ithome.com.tw/upload/images/20230919/20151470N6Vna1KUDo.png

  4. 多個分隔符:有時候,字串中的分隔符可能不只一種,String.Split 可以使用多個分隔符號字元,你可以將多個分隔符作為陣列傳遞給 Split 方法。

    string data = "apple,banana;cherry date";
    char[] separators = new char[] { ',', ';', ' ' };
    string[] fruits = data.Split(separators, StringSplitOptions.RemoveEmptyEntries);
    
    foreach (string fruit in fruits)
    {
        Console.WriteLine(fruit);
    }
    

    這個範例中,字串使用逗號、分號和空格作為分隔符,並且使用 StringSplitOptions.RemoveEmptyEntries 選項來移除空白項目。
    https://ithelp.ithome.com.tw/upload/images/20230919/20151470SgLCnLWAp6.png
    StringSplitOptions.RemoveEmptyEntries 是一個列舉(enum),用於指定在使用 string.Split 方法時,是否要刪除結果中的空字串(即空白的子字串)。

    如果再詳細點解釋:
    string.Split 方法就是上面提到用來將一個字串拆分成多個子字串的方法,拆分後的子字串將儲存在一個陣列中。

    StringSplitOptions 是一個列舉,它有兩個成員:

    1. None:表示不應刪除任何空字串。這意味著如果在拆分字串時有連續的分隔符(例如 "A,,B"),那麼結果中將包含空字串(["A", "", "B"])。
    2. RemoveEmptyEntries:表示應該刪除所有空字串。這意味著在拆分字串時,任何連續的分隔符都不會生成空字串,它們將被從結果中完全移除。

    這裡舉例說明:

    string text = "A,,B,,C";
    string[] parts = text.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
    

    把字串 text 用逗號作為分隔符拆分成多個子字串,可以看到A後面有一個空字串。使用**StringSplitOptions.RemoveEmptyEntries**,把所有空字串都移除,所以 parts 陣列就只包含非空的子字串:["A", "B", "C"]。

    看完上面的小範例,應該可以理解**StringSplitOptions.RemoveEmptyEntries** 是用來控制在使用 string.Split 方法時是否要移除空字串的選項,這對於處理分隔符之間可能有多個連續空白的情況非常有用。

這些範例展示了如何使用 Split 方法將字串分割成陣列,根據不同的分隔符和情境進行調整。你可以根據你的需求選擇適當的方法和分隔符。

正規的寫法

int[] numbers = new int[] { 1, 4, 8, 31, 99, 22 };
string[] items = new string[] {"叫", "A", "B", "D", "D" ,"沙丘"};

語法糖(跟上面正規比起來,變成簡化為直接賦值)

int[] numbers = { 1, 4, 8, 31, 99, 22 };
string[] items = { "叫", "A", "B", "D", "D", "沙丘" };
string products = "Pchome,Momo,Mamamoo,Keyboard";
			string[]prodItems= products.Split(',');

			for (int i = 0; i < prodItems.Length; i++) {
				Console.WriteLine(prodItems[i]);
			}

上面這段程式碼使用了for迴圈 結合Array陣列。

這段程式碼主要執行幾個步驟:

  1. 創建一個名為 products 的字串,其中包含多個產品名稱,這些名稱用逗號 , 分隔開來。
  2. 使用 上面提到的 Split 方法,把 products 字串根據逗號 , 進行分割,分割後的結果存在 prodItems 字串陣列中。把 products 字串分割成多個子字串,每個子字串代表一個產品名稱。
  3. 使用 for 迴圈遍歷 prodItems 陣列,並使用 Console.WriteLine 方法把每個產品名稱一個個輸出到控制台。

https://ithelp.ithome.com.tw/upload/images/20230919/20151470yYFVVCm6zA.png

再來是關於傳回指定年份中每個月的天數的兩個不同實現方式的寫法舉例。

/// <summary>
/// 傳回指定年分,每一個月的天數
/// </summary>
/// <param name="year"></param>
/// <returns></returns>
static string[] GetDaysOfMonth(int year){ }

static ( int, int,int,int, int, int, int, int, int, int, int, int,) GetDaysOfMonth2(int year) { }

上面兩個靜態方法都接受一個 year 參數(也就是後面的int year),代表指定的年份。

可以看到上面第一個GetDaysOfMonth靜態方法,目標是返回一個字串陣列,該陣列包含了指定年份中每個月的天數。

第二個GetDaysOfMonth2靜態方法與第一個方法不同,這個方法的返回類型是一個包含12個整數的元組(tuple),每個整數代表一個月的天數。元組的結構是 (int, int, int, int, int, int, int, int, int, int, int, int),其中每個元素位置對應一個月。但這個方法如果日子變多就會很難抓取到需要的資料。

這個程式碼片段是關於解析設定字串的示例,它將一個包含多個設定值的字串分割成單獨的變數。以下是一些其他示範例子,演示如何解析不同格式的設定字串:

  1. 解析帶有冒號的設定字串:

    string settings = "Server:192.168.1.1;Port:8080;Username:john;Password:secret";
    string[] settingsArray = settings.Split(';');
    Dictionary<string, string> settingsDict = new Dictionary<string, string>();
    
    foreach (string setting in settingsArray)
    {
        string[] parts = setting.Split(':');
        if (parts.Length == 2)
        {
            string key = parts[0];
            string value = parts[1];
            settingsDict[key] = value;
        }
    }
    
    if (settingsDict.ContainsKey("Server") && settingsDict.ContainsKey("Port"))
    {
        string server = settingsDict["Server"];
        int port = int.Parse(settingsDict["Port"]);
    		Console.WriteLine($"Server: {server}, Port: {port}");
    }
    else
    {
        Console.WriteLine("設定字串格式不正確");
    }
    

    這個例子中,設定字串包含了以冒號分隔的鍵值對,並使用字典來存儲這些設定。
    https://ithelp.ithome.com.tw/upload/images/20230919/20151470IBV0Yk2Kts.png

  2. 解析以空格分隔的設定字串:

    string settings = "LogLevel Info LogFile app.log";
    string[] settingsArray = settings.Split(' ');
    
    if (settingsArray.Length >= 3)
    {
        string logLevel = settingsArray[1];
        string logFile = settingsArray[2];
    		Console.Write($"{ logLevel} & {logFile}");
    }
    else
    {
        Console.WriteLine("設定字串格式不正確");
    }
    

    這個例子中,設定字串使用空格分隔,並且我們根據位置來解析不同的設定值。
    https://ithelp.ithome.com.tw/upload/images/20230919/20151470hzU4zVCawA.png
    無論設定字串的格式如何,通常的做法是使用 split方法將字串分割成子字串,根據分割後的部分提取所需的設定值。

可以使用這些值執行相應的操作。如果設定字串格式不正確,則應該像上面的程式碼一樣,可以進行錯誤處理。


順利完成第七天!!!


上一篇
Day 6 綜合小練習
下一篇
Day 8 數值格式化 StringBuilder 類型轉換
系列文
30天開啟.NET後端工程師的旅程30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言